#include "camera.h"

#include "material.h"
#include "pdf.h"

camera::camera(double aspect_ratio, int image_width, double vfov, point3 center, point3 lookat, vec3 vup)
	: _aspect_ratio(aspect_ratio)
	, _image_width(image_width)
	, _vfov(vfov)
	, _center(center)
{
	_image_height = int(_image_width / _aspect_ratio);
	_image_height = (_image_height < 1) ? 1 : _image_height;

	// Calculate the u,v,w unit basis vectors for the camera coordinate frame.
	w = unit_vector(_center - lookat);
	u = unit_vector(cross(vup, w));
	v = cross(w, u);

	_viewport_height = 0.f;
    _viewport_width = 0.f;
}

void camera::render(const hittable& world)
{
	std::cout << "P3\n" << _image_width << ' ' << _image_height << "\n255\n";

	initialize();

	for (int j = 0; j < _image_height; j++) {
		std::clog << "\rScanlines remaining: " << (_image_height - j) << ' ' << std::flush;
		for (int i = 0; i < _image_width; i++) {
			color pixel_color(0, 0, 0);
			for (int s_j = 0; s_j < sqrt_spp; s_j++) {
				for (int s_i = 0; s_i < sqrt_spp; s_i++) {
					ray r = get_ray(i, j, s_i, s_j);
					pixel_color += ray_color(r, max_depth, world);
				}
			}
			write_color(std::cout, pixel_color * pixel_samples_scale);
		}
	}

	std::clog << "\rDone.                 \n";
}

color camera::ray_color(const ray& r, int depth, const hittable& world) const
{
	// If we've exceeded the ray bounce limit, no more light is gathered.
	if (depth <= 0)
		return color(0, 0, 0);

	hit_record rec;

	// If the ray hits nothing, return the background color.
	if (!world.hit(r, interval(0.001, infinity), rec))
		return background;

	scatter_record srec;
	color color_from_emission = rec.mat->emitted(r, rec, rec.u, rec.v, rec.p);// 自发光颜色
	if (!rec.mat->scatter(r, rec, srec))
		return color_from_emission;

	if (srec.skip_pdf) {
		return srec.attenuation * ray_color(srec.skip_pdf_ray, depth - 1, world);
	}

	ray scattered;
	double pdf_value;// 向某个方向采样的pdf
	double scattering_pdf;// 材质散射到此采样方向的pdf
	color color_from_scatter;
	pdf* cal_pdf;

	if (nullptr == lights)
	{
		cal_pdf = new cosine_pdf(rec.normal);
	}
	else
	{
		auto light_ptr = make_shared<hittable_pdf>(*lights, rec.p);
		cal_pdf = new mixture_pdf(light_ptr, srec.pdf_ptr);
	}

	scattered = ray(rec.p, cal_pdf->generate(), r.time());
	pdf_value = cal_pdf->value(scattered.direction());

	delete cal_pdf;// 在下次递归之前，将内存回收

	color sample_color = ray_color(scattered, depth - 1, world);
	scattering_pdf = rec.mat->scattering_pdf(r, rec, scattered);
	color_from_scatter = (srec.attenuation * scattering_pdf * sample_color) / pdf_value;

	return color_from_emission + color_from_scatter;
}

ray camera::get_ray(int i, int j, int s_i, int s_j) const
{
	// Construct a camera ray originating from the defocus disk and directed at a randomly
	// sampled point around the pixel location i, j.

	auto offset = sample_square_stratified(s_i, s_j);
	auto pixel_sample = pixel00_loc
		+ ((i + offset.x()) * pixel_delta_u)
		+ ((j + offset.y()) * pixel_delta_v);

	auto ray_origin = (defocus_angle <= 0) ? _center : defocus_disk_sample();
	auto ray_direction = pixel_sample - ray_origin;
	auto ray_time = random_double();

	return ray(ray_origin, ray_direction, ray_time);
}

vec3 camera::sample_square() const
{
	// Returns the vector to a random point in the [-.5,-.5]-[+.5,+.5] unit square.
	return vec3(random_double() - 0.5, random_double() - 0.5, 0);
}

point3 camera::defocus_disk_sample() const
{
	// Returns a random point in the camera defocus disk.
	auto p = random_in_unit_disk();
	return _center + (p[0] * defocus_disk_u) + (p[1] * defocus_disk_v);
}

vec3 camera::sample_square_stratified(int s_i, int s_j) const
{
	// Returns the vector to a random point in the square sub-pixel specified by grid
	// indices s_i and s_j, for an idealized unit square pixel [-.5,-.5] to [+.5,+.5].

	auto px = ((s_i + random_double()) * recip_sqrt_spp) - 0.5;
	auto py = ((s_j + random_double()) * recip_sqrt_spp) - 0.5;

	return vec3(px, py, 0);
}

void camera::initialize()
{
	auto theta = degrees_to_radians(_vfov);
	auto h = std::tan(theta / 2);
	_viewport_height = 2 * h * focus_dist;

	_viewport_width = _viewport_height * (double(_image_width) / _image_height);// 不使用aspect_ratio计算，精度问题
	auto viewport_u = _viewport_width * u;// u方向坐标总变化量
	auto viewport_v = -_viewport_height * v;// v方向坐标总变化量

	pixel_delta_u = viewport_u / _image_width;// per像素 u方向坐标变化量
	pixel_delta_v = viewport_v / _image_height;// per像素 v方向坐标变化量

	auto viewport_upper_left = _center - (focus_dist * w) - viewport_u / 2 - viewport_v / 2;// 左上角坐标
	pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);// 左上角中心点坐标

	// Calculate the camera defocus disk basis vectors.
	auto defocus_radius = focus_dist * std::tan(degrees_to_radians(defocus_angle / 2));
	defocus_disk_u = u * defocus_radius;
	defocus_disk_v = v * defocus_radius;

	sqrt_spp = int(std::sqrt(samples_per_pixel));
	pixel_samples_scale = 1.0 / (sqrt_spp * sqrt_spp);
	recip_sqrt_spp = 1.0 / sqrt_spp;
}
